package com.rizhov.graphmodels;

import java.util.ArrayList;

import com.rizhov.enumerations.GraphModelType;
import com.rizhov.enumerations.GraphOrientation;

/**
 * 
 * @author Pavel Ryzhov
 * 
 */
public class ListGraphModel implements GraphModel {

    /**
     * can be DIRECTED_GRAPH or UNDIRECTED_GRAPH
     * 
     * @see GraphOrientation
     * **/
    private GraphOrientation dirrectionType;

    /** edges count in graph **/
    private int edgeCount = 0;

    /** vertex count in graph **/
    private int vertexCount = 0;

    /** that collection contains all edges of graph **/
    private ArrayList<ArrayList<ListModelEdge>> edgeArr = new ArrayList<ArrayList<ListModelEdge>>();

    /** vertex store collection **/
    private ArrayList<Object> vertexArr = new ArrayList<Object>();

    /** entity that stores edge information **/
    private class ListModelEdge {

	/** vertex index graph edge starts from **/
	private int startVertexIndex;

	/** the weight of current edge **/
	private Object edgeWeight;

	/** the data of current edge **/
	private Object edgeData;

	private ListModelEdge() {

	}

	private ListModelEdge(Object edgeWeight, Object edgeData, int startVertexIndex) {
	    this.edgeWeight = edgeWeight;
	    this.edgeData = edgeData;
	    this.startVertexIndex = startVertexIndex;
	}
    }

    public ListGraphModel() {
	dirrectionType = GraphOrientation.ORIENTED_GRAPH;
    }

    @Override
    public Object clone() {
	return null;
    }

    @Override
    public void printGraphIntoConsole() {
	System.out.println(getStatisticsString());
    }

    @Override
    public int getVertexCount() {
	return vertexCount;
    }

    @Override
    public int getEdgeCount() {
	return edgeCount;

    }

    @Override
    public GraphOrientation getGraphDirectionType() {
	return dirrectionType;
    }

    @Override
    public void setGraphOrientation(GraphOrientation graphOrientation) {
	if (dirrectionType == graphOrientation) {
	    return;
	} else if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH
		&& graphOrientation == GraphOrientation.ORIENTED_GRAPH) {
	    // just dirrectionType has been changed (below)
	} else if (dirrectionType == GraphOrientation.ORIENTED_GRAPH
		&& graphOrientation == GraphOrientation.UNORIENTED_GRAPH) {
	    for (int i = 0; i < vertexCount; i++) {
		for (int j = 0; j < vertexCount; j++) {
		    if (isEdgeExist(i, j) && isEdgeExist(i, j)) {
			setEdgeWeight(j, i, getEdgeWeight(i, j));
			setEdgeData(j, i, getEdgeData(i, j));
		    }
		    if (isEdgeExist(i, j)) {
			addEdge(j, i, getEdgeWeight(i, j));
			if (isEdgeExist(j, i)) {
			    setEdgeWeight(j, i, getEdgeWeight(i, j));
			    setEdgeData(j, i, getEdgeData(i, j));
			}
		    }
		    if (isEdgeExist(j, i)) {
			addEdge(i, j, getEdgeWeight(j, i));
			if (isEdgeExist(i, j)) {
			    setEdgeWeight(i, j, getEdgeWeight(j, i));
			    setEdgeData(i, j, getEdgeData(j, i));
			}
		    }
		}
	    }
	}
	dirrectionType = graphOrientation;
    }

    @Override
    public GraphModelType getGraphModelType() {
	return GraphModelType.LIST_MODEL_TYPE;
    }

    @Override
    public double getGraphSaturationRatio() {
	if (edgeCount == 0) {
	    return 0;
	} else {
	    double dEdgeCount = edgeCount;
	    double dVertexCount = vertexCount;
	    return (dEdgeCount) / (dVertexCount * (dVertexCount - 1));
	}
    }

    @Override
    public boolean addVertex(Object vertexData) {
	for (int i = 0; i < vertexCount; i++) {
	    if (vertexArr.get(i).equals(vertexData)) {
		return false;
	    }
	}
	vertexCount++;
	vertexArr.add(vertexData);
	edgeArr.add(new ArrayList<ListModelEdge>());
	return true;
    }

    @Override
    public int getVertexIndex(Object vertexData) {
	for (int i = 0; i < vertexCount; i++) {
	    if (vertexArr.get(i).equals(vertexData)) {
		return i;
	    }
	}
	return -1;
    }

    @Override
    public boolean removeVertex(Object vertexData) {

	int deletedIndex = -1;

	for (int i = 0; i < vertexCount; i++) {
	    if (vertexArr.get(i).equals(vertexData)) {
		deletedIndex = i;

		for (int j = 0; j < vertexCount; j++) {
		    ArrayList<Integer> indexesToBeDeleted = new ArrayList<Integer>();
		    for (int k = 0; k < edgeArr.get(j).size(); k++) {
			if (edgeArr.get(j).get(k).startVertexIndex == deletedIndex) {
			    indexesToBeDeleted.add(k);
			}
		    }
		    for (int k = indexesToBeDeleted.size() - 1; k >= 0; k--) {
			edgeArr.get(j).remove((int) indexesToBeDeleted.get(k));
		    }
		    edgeCount = edgeCount - indexesToBeDeleted.size();
		}
		vertexCount--;
		vertexArr.remove(deletedIndex);
		edgeCount -= edgeArr.get(deletedIndex).size();
		edgeArr.remove(deletedIndex);
		break;
	    }
	}

	for (int i = 0; i < edgeArr.size(); i++) {
	    for (int j = 0; j < edgeArr.get(i).size(); j++) {
		if (edgeArr.get(i).get(j).startVertexIndex > deletedIndex) {
		    edgeArr.get(i).get(j).startVertexIndex = edgeArr.get(i).get(j).startVertexIndex - 1;
		}
	    }
	}

	return true;
    }

    @Override
    public boolean addEdge(int startIndex, int endIndex, Object edgeWeight) {
	if (startIndex == endIndex) {
	    return false;
	}
	if (rangeCheck(startIndex, endIndex)) {
	    ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
	    for (int i = 0; i < edgeList.size(); i++) {
		if (edgeList.get(i).startVertexIndex == startIndex) {
		    return false;
		}
	    }
	    edgeCount++;
	    ListModelEdge tempEdge = new ListModelEdge();
	    tempEdge.edgeWeight = edgeWeight;
	    tempEdge.edgeData = "0";
	    tempEdge.startVertexIndex = startIndex;
	    edgeList.add(tempEdge);
	    if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
		ArrayList<ListModelEdge> edgeList2 = edgeArr.get(startIndex);
		for (int i = 0; i < edgeList2.size(); i++) {
		    if (edgeList2.get(i).startVertexIndex == endIndex) {
			return false;
		    }
		}
		edgeCount++;
		ListModelEdge tempEdge2 = new ListModelEdge();
		tempEdge2.edgeWeight = edgeWeight;
		tempEdge2.edgeData = "0";
		tempEdge2.startVertexIndex = endIndex;
		edgeList2.add(tempEdge2);
	    }
	    return true;
	} else {
	    return false;
	}
    }

    @Override
    public boolean removeEdge(int startIndex, int endIndex) {
	if (dirrectionType == GraphOrientation.ORIENTED_GRAPH) {
	    if (rangeCheck(startIndex, endIndex)) {
		ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
		for (int i = 0; i < edgeList.size(); i++) {
		    if (edgeList.get(i).startVertexIndex == startIndex) {
			edgeCount--;
			edgeList.remove(i);
			return true;
		    }
		}
	    }
	} else {
	    if (rangeCheck(startIndex, endIndex)) {
		ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
		for (int i = 0; i < edgeList.size(); i++) {
		    if (edgeList.get(i).startVertexIndex == startIndex) {
			edgeCount--;
			edgeList.remove(i);
		    }
		}
		edgeList = edgeArr.get(startIndex);
		for (int i = 0; i < edgeList.size(); i++) {
		    if (edgeList.get(i).startVertexIndex == endIndex) {
			edgeCount--;
			edgeList.remove(i);
			return true;
		    }
		}
	    }
	}
	return false;
    }

    @Override
    public boolean isEdgeExist(int startIndex, int endIndex) {
	if (rangeCheck(startIndex, endIndex)) {
	    ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
	    for (int i = 0; i < edgeList.size(); i++) {
		if (edgeList.get(i).startVertexIndex == startIndex) {
		    return true;
		}
	    }
	    return false;
	} else {
	    return false;
	}
    }

    @Override
    public boolean setEdgeWeight(int startIndex, int endIndex, Object weight) {
	if (rangeCheck(startIndex, endIndex)) {
	    ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
	    for (int i = 0; i < edgeList.size(); i++) {
		if (edgeList.get(i).startVertexIndex == startIndex) {
		    edgeList.get(i).edgeWeight = weight;
		    if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
			ArrayList<ListModelEdge> edgeList2 = edgeArr.get(startIndex);
			for (int j = 0; j < edgeList2.size(); j++) {
			    if (edgeList2.get(j).startVertexIndex == endIndex) {
				edgeList2.get(j).edgeWeight = weight;
			    }
			}
		    }
		    return true;
		}
	    }
	}
	return false;
    }

    @Override
    public Object getEdgeWeight(int startIndex, int endIndex) {
	if (rangeCheck(startIndex, endIndex)) {
	    ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
	    for (int i = 0; i < edgeList.size(); i++) {
		if (edgeList.get(i).startVertexIndex == startIndex) {
		    return edgeList.get(i).edgeWeight;
		}
	    }
	}
	return null;
    }

    @Override
    public boolean setEdgeData(int startIndex, int endIndex, Object data) {
	if (rangeCheck(startIndex, endIndex)) {
	    ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
	    for (int i = 0; i < edgeList.size(); i++) {
		if (edgeList.get(i).startVertexIndex == startIndex) {
		    edgeList.get(i).edgeData = data;
		    if (dirrectionType == GraphOrientation.UNORIENTED_GRAPH) {
			ArrayList<ListModelEdge> edgeList2 = edgeArr.get(startIndex);
			for (int j = 0; j < edgeList2.size(); j++) {
			    if (edgeList2.get(j).startVertexIndex == endIndex) {
				edgeList2.get(j).edgeData = data;
			    }
			}
		    }
		    return true;
		}
	    }
	}
	return false;
    }

    @Override
    public Object getEdgeData(int startIndex, int endIndex) {
	if (rangeCheck(startIndex, endIndex)) {
	    ArrayList<ListModelEdge> edgeList = edgeArr.get(endIndex);
	    for (int i = 0; i < edgeList.size(); i++) {
		if (edgeList.get(i).startVertexIndex == startIndex) {
		    return edgeList.get(i).edgeData;
		}
	    }
	}
	return null;
    }

    @Override
    public String getStatisticsString() {
	StringBuilder sb = new StringBuilder();
	sb.append("Edges' information: \n");
	for (int i = 0; i < vertexCount; i++) {
	    sb.append(i + ") ");
	    for (int j = 0; j < edgeArr.get(i).size(); j++) {
		if (j != 0) {
		    sb.append("->");
		}
		sb.append("[" + edgeArr.get(i).get(j).edgeData + ", "
			+ edgeArr.get(i).get(j).edgeWeight + "("
			+ getVertexData(edgeArr.get(i).get(j).startVertexIndex) + ")" + "]");
	    }
	    sb.append("\n");
	}
	sb.append("Vertex data: " + vertexArr.toString());
	return sb.toString();
    }

    @Override
    public Object getVertexData(int index) {
	if (index >= 0 && index < vertexCount) {
	    return vertexArr.get(index);
	} else {
	    return null;
	}
    }

    private boolean rangeCheck(int startIndex, int endIndex) {
	if (startIndex >= 0 && startIndex < vertexCount && endIndex >= 0 && endIndex < vertexCount
		&& vertexCount != 0) {
	    return true;
	} else {
	    return false;
	}

    }

    @Override
    public boolean setVertexData(Object newVertexData, Object oldVertexData) {
	for (int i = 0; i < vertexArr.size(); i++) {
	    if (vertexArr.get(i).equals(oldVertexData)) {
		vertexArr.set(i, newVertexData);
		return true;
	    }
	}
	return false;
    }
}
